﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Net;
using System.Xml;
using NetBee.Config;
using NetBee.Entity.MySql;
namespace NetBee.Business
{
    public class Gatherer
    {
        private static Dictionary<string, object> Lockers { set; get; }
        public Gatherer(NetBeeForm form, ushort parallelCount)
        {
            this.Form = form;
            this.ParallelCount = parallelCount;
            MySqlEntities = new NetBeeEntities();
            Lockers = new Dictionary<string, object>();
            Lockers.Add("SaveData", new object());
            Lockers.Add("GatherHtml", new object());
        }
        private int ParallelCount { set; get; }
        private NetBeeForm Form { set; get; }

        private static NetBeeEntities MySqlEntities { set; get; }
        public virtual void Gather(long taskId)
        {
            var tasks = MySqlEntities.NetBee_Task.AsNoTracking().Where(i => taskId==i.Id).ToList();
            foreach (var task in tasks)
            {
                var urls = MySqlEntities.NetBee_Url.Where(i => i.TaskId == task.Id);
                var rules = MySqlEntities.NetBee_GatherRules.AsNoTracking().Where(i => i.TaskId == task.Id).ToList<NetBee_GatherRules>();
                if (urls.Count() > 0)
                {
                    Gather(urls, task.EncodeName,rules);
                }
            }
        }
        public virtual void Gather(IEnumerable<NetBee_Url> urls, string encodeName,List<NetBee_GatherRules> rules)
        {
            var segCount = (int)Math.Ceiling(urls.Count() / (decimal)this.ParallelCount);
            
            for (var segIndex = 0; segIndex < segCount; segIndex++)
            {
                #region//多个线程下载html
                var seg = urls.Skip(segIndex * this.ParallelCount).Take(this.ParallelCount).ToList();
                var tasks = new List<System.Threading.Tasks.Task<dynamic>>(seg.Count);
                foreach (var url in seg)
                {
                    var request = GetHttpWebRequest(url.Url);
                    var taskId = url.TaskId;
                    var urlId = url.Id;
                    var source = url.Url;
                    var data = new LinkedList<KeyValuePair<string, List<string>>>();
                    var html = string.Empty;
                    var encode = encodeName;
                    var task = System.Threading.Tasks.Task<dynamic>.Factory.StartNew(() => GatherHtml(request, source, encode, taskId, urlId, Lockers["GatherHtml"]));
                    tasks.Add(task);
                }
                #endregion
                System.Threading.Tasks.Task<dynamic>.WaitAll(tasks.ToArray(), -1);
                foreach (var task in tasks)
                {
                    var ds = Analyze(task.Result.Html, rules);
                    SaveData(task.Result.TaskId, task.Result.UrlId, ds);
                }
            }
        }
        public virtual dynamic GatherHtml(HttpWebRequest request, string addr, string encodeName, long taskId, long urlId, object locker)
        {
            var response = request.GetResponse() as HttpWebResponse;//响应
            var encode = System.Text.Encoding.GetEncoding(encodeName);//响应正文的字符编码
            System.IO.StreamReader reader = null;//流读取器
            var html = string.Empty;
            #region//download html
            try
            {
                var stream = response.GetResponseStream();
                reader = new System.IO.StreamReader(stream, encode);
                html = reader.ReadToEnd();
            }
            catch
            {
                lock (Lockers["GatherHtml"])
                {
                    using (var entity = new NetBeeEntities())
                    {
                        var u = entity.NetBee_Url.Find(urlId);
                        u.Status = (int)response.StatusCode;
                        entity.SaveChanges();
                    }
                }
            }
            finally
            {
                this.Form.Print(this.Form.txt_Screen, addr + " --" + (int)response.StatusCode + System.Environment.NewLine);
                if (response != null)
                {
                    response.Close();
                }
                if (reader != null)
                {
                    reader.Close();
                }
                request = null;
            }
            #endregion
            return new { TaskId = taskId, UrlId = urlId, Html = html };
        }
        internal HttpWebRequest GetHttpWebRequest(string url)
        {
            NetBeeSettings setting = new NetBeeSettings();
            var request = HttpWebRequest.Create(url) as HttpWebRequest;
            request.Timeout = setting.RequestTimeout;
            return request;
        }
        public virtual DataSet Analyze(string html, List<NetBee_GatherRules> rules)
        {
            LinkedList<KeyValuePair<string, List<string>>> data = new LinkedList<KeyValuePair<string, List<string>>>();
            var doc = new System.Html.HtmlDocument();
            if (string.IsNullOrEmpty(html))
            {
                #region//提示用户
                    #if DEBUG
                        System.Diagnostics.Debug.Assert(false, "html 为空~");
                    #endif
                        return new DataSet("data");
                #endregion
            }
            doc.LoadHTML(html);
            foreach (var rule in rules)
            {
                XmlNodeList matches = null;
                try
                {
                    matches = doc.SelectNodes(rule.Xpath);
                }
                catch (Exception e)
                {
                    System.Diagnostics.Debug.Assert(false, "Xpath匹配错误:"+rule.Xpath, e.StackTrace);
                }
                if (matches != null)
                {
                    var values = new List<string>(matches.Count);
                    foreach (XmlNode item in matches)
                    {
                        values.Add(item.Value);
                    }
                    data.AddLast(new KeyValuePair<string, List<string>>(rule.Name, values));
                }
            }
            return Convert(data);
        }
        public virtual string SaveData(long taskId, long urlId, DataSet ds)
        {
            lock (Lockers["SaveData"])
            {
                var id = string.Empty;
                if (ds.Tables.Count < 1)
                {
                    return "xml 为 空";
                }
                var entity = MySqlEntities.NetBee_GatherData.Create();
                entity.Data = ds.GetXml();
                entity.TaskId = taskId;
                entity.UrlId = urlId;
                MySqlEntities.NetBee_GatherData.Add(entity);
                MySqlEntities.SaveChanges();
                id = entity.Id.ToString();
                return id;
            }
        }
        public virtual DataSet Convert(LinkedList<KeyValuePair<string, List<string>>> data)
        {
            using (var ds = new DataSet("data"))
            {
                if (data.Count < 1)
                {
                    return ds;
                }
                #region//datatable
                using (var table = new DataTable("row"))
                {
                    var max = data.Max(i => i.Value.Count);//table 的最大行数
                    #region//初始化table
                    var groups = data.GroupBy(i => i.Key);

                    foreach (var group in groups)
                    {
                        table.Columns.Add(group.Key);
                    }
                    for (int i = 0; i < max; i++)
                    {
                        var row = table.NewRow();
                        table.Rows.Add(row);
                    }

                    #endregion
                    #region//将数据行添加到table
                    foreach (var kv in data)
                    {
                        for (int i = 0; i < max; i++)
                        {
                            var value = i < kv.Value.Count ? kv.Value[i] : string.Empty;
                            table.Rows[i][kv.Key] = value;
                        }
                    }
                    #endregion
                    if (table.Rows.Count > 0)
                    {
                        ds.Tables.Add(table);
                    }
                }
                #endregion
                return ds;
            }
        }
    }
}
